﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using Engine;
using JiraPresentation;
using Jira;
using SaJiraSync.Config.DownloadStatisticsWarning;
using SaJiraSync.Config.SubmitterFilters;
using log4net;
using log4net.Config;
using SaJiraSync.Config.Closer;
using SaJiraSync.Config.Email;
using SaJiraSync.Config.Jira;
using SaJiraSync.Config.Plugins;
using SmartAssembly;
using Utilities;
using Utilities.DynamicValues;
using WebClient = JiraRest.WebClient;

namespace SaJiraSync
{
    internal static class Program
    {
        private static readonly ILog s_Log = LogManager.GetLogger("SaJiraSync.Program");

        private static void Main()
        {
            XmlConfigurator.Configure();
            s_Log.Info("Started");

            // Only make this once, so we don't leak unmanaged memory for the generated assemblies.
            IDynamicValueCompiler<Func<Report, string>> dynamicValueCompiler = new DynamicValueCompiler<Func<Report, string>>();

            try
            {
                RunOnce(dynamicValueCompiler);
                s_Log.Info("I'll be back");
            }
            catch (WebException we)
            {
                s_Log.Fatal("WebException:", we);
                s_Log.Fatal(new StreamReader(we.Response.GetResponseStream()).ReadToEnd());
            }
            catch (Exception e)
            {
                s_Log.Fatal("Exiting", e);
            }
        }

        private static void RunOnce(IDynamicValueCompiler<Func<Report, string>> dynamicValueCompiler)
        {
            Configuration appConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

            if (!appConfig.HasFile)
            {
                throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, "Configuration file '{0}' not found", appConfig.FilePath));
            }

            JiraSection jiraSection = appConfig.GetSection("jira") as JiraSection;
            if (jiraSection == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Must specify jira section in {0}", appConfig.FilePath));
            }

            EmailSection emailSection = appConfig.GetSection("email") as EmailSection;
            if (emailSection == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Must specify email section in {0}", appConfig.FilePath));
            }

            DownloadStatisticsWarningSection downloadStatisticsWarningSection = appConfig.GetSection("downloadstatisticswarning") as DownloadStatisticsWarningSection;
            if (downloadStatisticsWarningSection == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Must specify download-statistics warning section in {0}", appConfig.FilePath));
            }

            CloserSection closerSection = appConfig.GetSection("closer") as CloserSection;
            if (closerSection == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Must specify closer section in {0}", appConfig.FilePath));
            }

            SubmitterFiltersSection filterSection = appConfig.GetSection("submitterFilters") as SubmitterFiltersSection;
            if (filterSection == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Must specify submitterFilters section in {0}", appConfig.FilePath));
            }

            ConfigurationSectionGroup pluginConfig = appConfig.GetSectionGroup("pluginConfig");

            List<IPluginComponent<JiraBug, JiraBugDef>> plugins = new List<IPluginComponent<JiraBug, JiraBugDef>>();
            PluginsSection pluginsSection = appConfig.GetSection("plugins") as PluginsSection;
            if (pluginsSection != null)
            {
                foreach (PluginsElement plugin in pluginsSection.BugProcessors)
                {
                    Type pluginType = Type.GetType(plugin.Type);
                    if (pluginType == null) throw new InvalidOperationException("Plugin type " + plugin.Type + " not found");

                    if (pluginType.IsGenericType)
                    {
                        pluginType = pluginType.MakeGenericType(typeof(JiraBug), typeof(JiraBugDef));
                    }

                    ConstructorInfo ctor = pluginType.GetConstructor(new[] { typeof(ConfigurationSectionGroup) });
                    object[] parameters;

                    if (ctor != null)
                    {
                        parameters = new[] { pluginConfig };
                    }
                    else
                    {
                        ctor = pluginType.GetConstructor(Type.EmptyTypes);
                        parameters = null;
                    }

                    plugins.Add((IPluginComponent<JiraBug, JiraBugDef>)ctor.Invoke(parameters));
                }
            }

            RunOnce(dynamicValueCompiler, jiraSection, emailSection, downloadStatisticsWarningSection, closerSection, plugins, filterSection);
        }

        private static void RunOnce(IDynamicValueCompiler<Func<Report, string>> dynamicValueCompiler, JiraSection jiraSection, EmailSection emailSection, DownloadStatisticsWarningSection downloadStatisticsWarningSection, CloserSection closerSection, IEnumerable<IPluginComponent<JiraBug, JiraBugDef>> plugins, SubmitterFiltersSection filtersSection)
        {
            JiraConfig jiraConfig = new JiraConfig(
                jiraSection.Login.Username,
                jiraSection.Login.Password,
                jiraSection.Search.MaxNumResults,
                jiraSection.OpenGrokServer != null ? jiraSection.OpenGrokServer.Server : null,
                jiraSection.Fields.ExceptionTypeField.Name,
                jiraSection.Fields.MethodTypeField.Name,
                jiraSection.Fields.MethodField.Name,
                jiraSection.Fields.ExceptionReportHashField.Name,
                jiraSection.Fields.EarliestBuildSeenField.Name,
                jiraSection.Fields.LatestBuildSeenField.Name,
                jiraSection.Fields.BuildFixedOnField.Name,
                jiraSection.Fields.AutomaticEmailField.Name,
                jiraSection.Fields.ReportCountField.Name,
                jiraSection.ResolvedStatuses.Select(x => x.Name).ToList(),
                jiraSection.FixedResolutions.Select(x => x.Name).ToList(),
                jiraSection.Actions.CloseAction.Name,
                jiraSection.Actions.CloseAction.Resolution,
                jiraSection.Actions.ReopenAction.Name,
                jiraSection.IssueTypes.ProjectLinkIssueType.Name,
                jiraSection.IssueTypes.BugIssueType.Name
                );

            IEmailSender emailSender = emailSection.Enabled ? new SmtpEmailSender(emailSection.Smtp.Host) : NullEmailSender.Instance;

            RunOnce(dynamicValueCompiler, jiraConfig, emailSender, closerSection, plugins, emailSection, downloadStatisticsWarningSection, filtersSection);
        }

        private static void RunOnce(IDynamicValueCompiler<Func<Report, string>> dynamicValueCompiler, JiraConfig jiraConfig, IEmailSender emailSender, CloserSection closerSection, IEnumerable<IPluginComponent<JiraBug, JiraBugDef>> plugins, EmailSection emailSection, DownloadStatisticsWarningSection downloadStatisticsWarningSection, SubmitterFiltersSection filtersSection)
        {
            IReportRepository reportRepository = new ReportRepository();

            s_Log.Info("Connecting to JIRA");
            JiraConnection jiraConnection = new JiraConnection(jiraConfig.Username, jiraConfig.Password);
            JiraBugTracker bugTracker = new JiraBugTracker(jiraConfig, jiraConnection, dynamicValueCompiler);
            JiraBugPresenter bugPresenter = new JiraBugPresenter(bugTracker);
            IDictionary<string, string> emailTemplates = emailSection.Templates.ToDictionary(t => t.Name, t => t.Body, StringComparer.InvariantCultureIgnoreCase);
            HashSet<string> internalSubmitterIpsAndGuids = filtersSection.InternalSubmitterFilters.Select(x => x.IpAddress).SelectMany(Helpers.GetAllFormsOfIpAddress).ToHashSet(StringComparer.OrdinalIgnoreCase);

            using (SyncEngine<JiraBug, JiraBugDef> syncEngine = new SyncEngine<JiraBug, JiraBugDef>(bugPresenter, bugTracker, reportRepository, emailSender, emailTemplates, internalSubmitterIpsAndGuids, plugins))
            {
                s_Log.Info("Syncing unprocessed reports to JIRA");
                syncEngine.SyncReportsToBugTracker();
            }

            CloseEngine<JiraBug, JiraBugDef> closeEngine = new CloseEngine<JiraBug, JiraBugDef>(bugTracker, closerSection.Prefix.Jql, closerSection.Searches.Select(x => x.Jql).ToHashSet());
            s_Log.Info("Closing reports in JIRA");
            closeEngine.CloseReportsInBugTracker();
        }
    }
}
